package com.innotek.testworkstation.common.restassured;

import com.alibaba.fastjson.JSONObject;
import com.innotek.testworkstation.common.restassured.apipage.ApiModel;
import com.innotek.testworkstation.common.restassured.testcase.CaseStepModel;
import com.innotek.testworkstation.config.Constant;
import com.innotek.testworkstation.util.*;
import io.qameta.allure.*;
import io.restassured.builder.ResponseBuilder;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static com.innotek.testworkstation.util.DecodeEncode.encodeByMd5;
import static com.innotek.testworkstation.util.Time.getTimeStamp;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;

/**
 * @ClassName RunRequest
 * @Description 测试用例按接口请求总方法类
 * @Author wangx
 * @Date 2021/7/23 下午1:41
 * @Version 1.0
 **/

@Slf4j
public class RunRequest {

    /** 替换请求参数 */
    private Map<String, Object> repalceData;

    /**
     *  返回验证map列表
     */
    private List<HashMap<String, Object>> verifyList;

    /** 需要保存的返回数据path */
    private HashMap<String, Object> savePathMap = null;

    private List<String> saveParamList = null;

    /**
     *  用例前置操作
     */
    private HashMap<String, Object> before;


    /** 测试步骤描述 */
    private String stepDescription;

    /** 接口所属项目名 */
    private String project;

    /** 接口请求的非json参数  */
    private HashMap<String, Object> param=null;
    /** 接口请求的json参数 */
    private HashMap<String, Object> json=null;
    /** 接口请求的POST方法，值为请求的uri */
    private String post=null;
    /** 接口请求的GET方法，值为请求的uri */
    private String get=null;
    /** 最终的请求Body */
    private String reqBody = null;
    /** 是否有测试用例标志 */
    private Boolean haveCase = false;

    /**
     * 发送http请求,带请求参数替换和返回校验
     * @param project 项目名
     * @param api 请求接口类
     * @param step 测试用例类
     */
    public Response run(String project, ApiModel api, CaseStepModel step)
    {
        this.repalceData=step.params;
        this.verifyList=step.verify;
        this.savePathMap=step.save;
        this.saveParamList=step.saveParams;
        this.stepDescription=step.description;
        this.haveCase = true;
        this.before = step.before;
        return run(project, api);
    }

    public Response run(String project, ApiModel api) {

        if(api.json!=null){
            // 这里map需要深度拷贝，不影响原api的数据
            this.json = StringUtils.clone(api.json);
        }
        if(api.param!=null){
            this.param = StringUtils.clone(api.param);
        }
        if(api.post !=null){
            this.post = api.post.trim();
        }
        if(api.get !=null){
            this.get = api.get.trim();
        }

        this.project=project.trim();

        // 如果有前置操作则先运行
        if(before !=null){
            before.forEach((k,v)->{
                if("sleep".equals(k)){
                    // 如果方法名是sleep，则运行等待函数
                    BuiltFunctionUtils.sleep(Integer.parseInt(v.toString()));
                }
            });

        }


//        ResponseValidationFailureListener failUre = (reqSpec, respSpec, resp) -> {
//            String errorLog = String.format("测试步骤：==== %s ==== 执行失败, 返回body: %s",stepDescription, resp.getBody().prettyPrint());
//            log.error(errorLog);
//        };


        // 若有参数，则把参数注入接口相应对象里; 多环境支持
//        RestAssured.config = RestAssured.config().logConfig(new LogConfig(new ToLoggerPrintStream(log).getPrintStream(), true));
//        RequestSpecification request = given().config(RestAssured.config().failureConfig(FailureConfig.failureConfig().with().failureListeners(failUre)));
        RequestSpecification request = given()
                .filter( (req, res, ctx) -> {
                    // 请求，返回过滤器
                    Response resOrigin=ctx.next(req, res);
                    ResponseBuilder responseBuilder=new ResponseBuilder().clone(resOrigin);
                    // 把测试用例的需要保存的请求参数保存到临时文件
                    if(haveCase){
                        formatSaveParam(req.getBody(), saveParamList);
                        reqBody = req.getBody();
                    }
                    //responseBuilder.setContentType(ContentType.JSON);
                    return responseBuilder.build();
                });
        // 接口方法
        String method="post";
        //接口请求的uri
        String uri="";
        if(post!=null){
            uri=post;
            method="post";
        }
        if(get!=null){
            uri=get;
            method="get";
        }

        if(param!=null){
            // param表示使用的是非json请求
            for(String key: param.keySet()){
                request.queryParam(key, apiReplace(param.get(key)));
            }
        }

        if(json!=null){
            // 采用深度拷贝方法
//            HashMap<String, Object> cloneJson = StringUtils.clone(json);
            mapForTraversal(json);
            // 添加固定的参数
            json.put("partnerNo", PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,String.format("%s.partnerNo",project)));
            json.put("appCode", PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,String.format("%s.appCode",project)));
            json.put("source", PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,String.format("%s.source",project)));
            json.put("timestamp", getTimeStamp());
            request.contentType(ContentType.JSON).body(json);
        }

        // 拼接请求地址
        String url = "";
        if(project.equals(Constant.PROJECT_WEB)){
            url = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"web.url") + uri;
        }else if(project.equals(Constant.PROJECT_POS)){
            url = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"pos.url") + uri;
        }else{
            throw new NullPointerException("没有找到url地址，请检查请求项目key");
        }
        if(!StringUtils.isEmpty(stepDescription)){
            log.info("测试步骤描述：{}",stepDescription);
        }
        // 请求
        Response response = request.when().log().all().request(method, url)
                .then().log().all().statusCode(200).extract().response();

        // 增加allure报告数据
        if(haveCase){
            allureToReport(stepDescription, response);
        }

        if (verifyList!=null){
            // 如果测试用例里需要返回验证，则按要求验证
            verifyCase(response, verifyList);
        }else{
            // 默认的请求成功判断
            response.then().body("msg",equalTo("处理成功"));
        }
        // 如果需要保存返回参数里的数据，那把数据保存到临时文件
        if (savePathMap!=null && !savePathMap.isEmpty()){
            saveResponseDataForPath(response, savePathMap);
        }

        return response;
    }

    /**
     * 接口yaml里的多层结构的键值对数据，需要递归遍历替换HashMap里的value数据
     * @param data 遍历键值对
     * @return 返回替换后的data
     */
    private HashMap<String, Object> mapForTraversal(HashMap<String,Object> data){
        Map<String, Object> saveMap = CachePool.getInstance().getCacheItems();
        // 把临时文件里的map插到repalceData里
        if(!saveMap.isEmpty()){
            if(this.repalceData==null){this.repalceData=saveMap;}
            else { this.repalceData.putAll(saveMap); }
        }
        
        try {
            Iterator<Map.Entry<String, Object>> iterator = data.entrySet().iterator();
            while(iterator.hasNext()){
                Map.Entry<String, Object> entry = iterator.next();
                Object o = entry.getValue();
                String key = entry.getKey();
                if(o instanceof HashMap){
                    data.put(key, o);
                    mapForTraversal((HashMap<String, Object>) o);
                }else if(o instanceof ArrayList){
                    data.put(key, o);
                    for(Object l: (ArrayList)o ){
                        mapForTraversal((HashMap<String,Object>) l);
                    }
                }
                else{
                    Object raw = apiReplace(o);
                    if (raw.toString().contains("${")) {
                        // 替换后若还是包含参数，说明没有可替换的参数，那不作为参数请求
                        iterator.remove();
                        /* 这里用迭代器的remove方法删除map里的key，解决了HashMap遍历时删除key报错的问题。
                           但无法解决多线程下线程安全的问题，后面如果用到多线程，则需要加锁。
                        */
                    }
                    else{
                        data.put(key, raw);
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return data;

    }

    /**
     * 接口里参数的value替换
     * @param raw
     * @return 替换后的数据（类型不变）
     */
    private Object apiReplace(Object raw) {
        // 如果repalceData为空，则直接返回(除了预定义参数)
        if(repalceData==null){
            return predefineReplace(raw);
        }
        if (raw.toString().contains("${")) {
            // 如果值是参数型开头${name},则进行替换
            for (Map.Entry<String, Object> kv : repalceData.entrySet()) {
                String matcher = "${" + kv.getKey() + "}";
                if (raw.toString().contains(matcher)) {
                    if(kv.getValue() instanceof String) {
                        // 字符串可以从中间替换，非字符串则直接赋值（类型不变）
                        raw = raw.toString().replace(matcher, kv.getValue().toString());
                    }else {
                        raw = kv.getValue();
                    }
                    if (kv.getKey().equals("password")){
                        raw = encodeByMd5(raw.toString());
                    }
                }
            }
        }
        if (raw.toString().contains("${")){
            // 如果遍历后还是包含参数，则进行预定义参数的替换
            raw = predefineReplace(raw);
        }

        return raw;
    }

    /**
     * 预定义接口参数的替换,若没有找到，则直接返回
     * @param raw 替换后的值
     */
    private Object predefineReplace(Object raw){
        Object returnData = raw;
        HashMap<String, Object> allP = PropertyUtil.getAllProperty(Constant.DEFAULT_CONFIG);
        String param = StringUtils.regexForOne("\\$\\{(.*?)\\}", raw.toString());
        if(param!=null && allP.containsKey(String.format("%s.%s",project, param))){
            // 如果参数在config里的特定参数里有，则取出来
            Object pro = allP.get(String.format("%s.%s",project, param));
            if(param.equals("password")){
                // 参数是密码， 则MD5加密
                pro = encodeByMd5(pro.toString());
            }
            if(pro != null){returnData = pro;}
        }else if (param!=null && allP.containsKey(param)){
            // 如果参数在config里的公众参数里有，则取出来
            Object pro = allP.get(param);
            if(pro != null){returnData = pro;}
        }

//
//        if(project.equals(Constant.PROJECT_WEB)){
//            // web参数
//            if("${mixAccount}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"web.mixAccount");
//                if(pro != null){returnData = pro;}
//            }else if("${password}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"web.password");
//                if(pro !=null){returnData = encodeByMd5(pro);}
//            }
//        }else if(project.equals(Constant.PROJECT_POS)){
//            // POS机参数
//            if("${account}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"pos.account");
//                if(pro != null){returnData = pro;}
//            }else if("${password}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"pos.password");
//                if(pro !=null){returnData = encodeByMd5(pro);}
//            }else if("${identifier}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"pos.identifier");
//                if(pro !=null){returnData = pro;}
//            }else if("${instId}".equals(raw.toString())){
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG,"pos.instId");
//                if(pro !=null){returnData = pro;}
//            }
//        }else {
//            // 公共参数
//            if ("${internalIdentifyCode}".equals(raw.toString())) {
//                // 代缴卡识别码
//                String pro = PropertyUtil.getProperty(Constant.DEFAULT_CONFIG, "internalIdentifyCode");
//                if (pro != null) { returnData = pro; }
//            }
//        }
        return returnData;
    }

    /**
     * 把测试用例的需要保存的请求参数保存到临时文件
     * @param bodyJson 实际的请求body（需要json格式的）
     * @param saveParamList 测试用例里的需要保存的参数
     */
    private void formatSaveParam(String bodyJson, List<String> saveParamList){
        if(saveParamList!=null && !saveParamList.isEmpty()){
            JSONObject fastJson = FastJsonUtils.getJson(bodyJson);
            JSONObject paramJson = fastJson.getJSONObject("bizReqData");
            HashMap<String,Object> saveParamMap = new HashMap<>();

            saveParamList.forEach(param->{
                Object value = paramJson.get(param);
                if(value!=null){
                    saveParamMap.put(param, value);
                }
            });
            if(!saveParamMap.isEmpty()){
                CachePool.getInstance().putCacheAllItem(saveParamMap);
                // 保存完后，清空list
                this.saveParamList = null;
            }
        }
    }

    /**
     * 用例返回验证方法
     * @param response 返回类
     * @param verifyList 用例里的验证列表
     */
    private void verifyCase(Response response, List<HashMap<String, Object>> verifyList){
        if(verifyList.size()>0){
            verifyList.forEach( verifyMap ->{
                try{
                    String path = verifyMap.get(Constant.RESPONSEPATH).toString();
                    String matcher = verifyMap.get(Constant.MATCHER).toString();
                    Object expect = verifyMap.get(Constant.EXPECT);

                    if(matcher.equals("equalTo")){
                        // 同Object.equals方法
                        response.then().body(path,equalTo(expect));
                    }else if(matcher.equals("hasItem")){
                        // 测试一个集合是否包含一个元素
                        response.then().body(path,hasItem(expect));
                    }
                    else {
                        // 先都是equalTo匹配，后面如果有加再说
                        response.then().body(path, equalTo(expect));
                    }
                }catch (AssertionError e){
                    throw new AssertionError(String.format("匹配错误：%s",e));
                }

            });
        }
    }

    /**
     * 从返回body里按用例的path获取值
     * @param response response类
     * @param savePathMap 用例里需要保存的返回数据path
     */
    private void saveResponseDataForPath(Response response, HashMap<String, Object> savePathMap){
        HashMap<String,Object> resultMap = new HashMap<>();
        if(savePathMap!=null && !savePathMap.isEmpty()){
            for (Map.Entry<String, Object> savePath : savePathMap.entrySet()){
                try {
                    // 获取path的值，注意这里的jsonPath用的是Groovy's GPath格式
                    Object value = response.body().path(savePath.getValue().toString());
                    if(value == null){
                        throw new NullPointerException(String.format("步骤:%s，获取返回数据失败：%s",stepDescription,savePath.getValue().toString()));
                    }else {
                        resultMap.put(savePath.getKey(),value);
                    }
                }catch (Exception e){
                    throw new IllegalStateException("返回路径path获取语法错误：" + savePath.getValue().toString(),e);
                }

            }
            if(!resultMap.isEmpty()){
                // 如果测试用例里需要保存数据，则把数据保存到临时文件
                CachePool.getInstance().putCacheAllItem(resultMap);
            }
        }
    }

    private void allureToReport(String stepDescription, @NotNull Response response){
        Allure.addAttachment(String.format("Step-请求: %s",stepDescription), reqBody);
        Allure.addAttachment(String.format("Step-响应: %s",stepDescription), response.getBody().asInputStream());
    }

}
